package com.bpmn.flow.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.bpmn.common.exception.ServiceException;
import com.bpmn.common.pojo.PageResult;
import com.bpmn.common.utils.MapUtils;
import com.bpmn.flow.controller.query.DefinitionQuery;
import com.bpmn.flow.controller.vo.ProcessDefinitionVo;
import com.bpmn.flow.service.DefinitionService;
import com.bpmn.utils.QueryUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static java.util.Collections.emptyList;

/**
 * @author wangzongrun
 * @description 流程定义服务实现类
 * @date 2024年12月27日 15:02
 */
@Slf4j
@Service
public class DefinitionServiceImpl implements DefinitionService {

    @Resource
    private RepositoryService repositoryService;

    /**
     * 根据流程定义的键获取流程定义列表
     *
     * @param definitionQuery 流程定义的键如果键为null，查询将返回所有流程定义
     * @return 匹配给定键的流程定义列表如果列表为空，则返回空列表
     */
    @Override
    public PageResult<ProcessDefinitionVo> getFlowDefinitions(DefinitionQuery definitionQuery) {
        // 创建一个流程定义查询对象这是查询流程定义的第一步
        ProcessDefinitionQuery query = QueryUtils.definitionQuery();
        // 如果键不为空，使用键来过滤流程定义
        query.processDefinitionKey(definitionQuery.getKey());
        // 设置查询结果按照部署ID降序排列，以确保最近部署的流程定义出现在列表的前面
        query.orderByProcessDefinitionVersion().desc();
        List<ProcessDefinition> definitionList = query.listPage(definitionQuery.getFirstNum(), definitionQuery.getPageSize());
        List<ProcessDefinitionVo> processDefinitionVos = BeanUtil.copyToList(definitionList, ProcessDefinitionVo.class);
        // 如果流程定义列表不为空，则遍历列表并设置部署时间
        if (CollUtil.isNotEmpty(processDefinitionVos)) {
            Set<String> deploymentIds = CollStreamUtil.toSet(definitionList, ProcessDefinition::getDeploymentId);
            List<Deployment> deploymentList = repositoryService.createDeploymentQuery().deploymentIds(new ArrayList<>(deploymentIds)).list();
            Map<String, Deployment> deploymentMap = CollStreamUtil.toIdentityMap(deploymentList, Deployment::getId);
            processDefinitionVos.forEach(item -> MapUtils.findAndThen(deploymentMap, item.getDeploymentId(), deployment -> item.setDeploymentTime(deployment.getDeploymentTime())));
        }
        // 执行查询并返回结果列表
        return new PageResult<>(processDefinitionVos, query.count());
    }

    /**
     * 激活或者挂起流程定义
     *
     * @param processDefinitionId 流程定义id
     */
    @Override
    public boolean updateDefinitionState(String processDefinitionId) {
        try {
            ProcessDefinition processDefinition = QueryUtils.definitionQuery().processDefinitionId(processDefinitionId).singleResult();
            //将当前为挂起状态更新为激活状态
            //参数说明：参数1：流程定义id,参数2：是否激活（true是否级联对应流程实例，激活了则对应流程实例都可以审批），
            //参数3：什么时候激活，如果为null则立即激活，如果为具体时间则到达此时间后激活
            if (processDefinition.isSuspended()) {
                //如果是激活，则先挂起其他流程定义
                suspendProcessDefinitionByKey(processDefinition.getKey());
                repositoryService.activateProcessDefinitionById(processDefinitionId, true, null);
            } else {
                // suspendProcessInstances = false，进行中的任务，不进行挂起。
                // 原因：只要新的流程不允许发起即可，老流程继续可以执行。
                repositoryService.suspendProcessDefinitionById(processDefinitionId, true, null);
            }
            return true;
        } catch (Exception e) {
            log.error("Failed to update process definition state: {}", e.getMessage(), e);
            throw new ServiceException("操作失败:" + e.getMessage());
        }
    }

    /**
     * 根据流程定义ID获取流程定义的XML
     *
     * @param processDefinitionId 流程定义的唯一标识符
     * @return 返回流程定义的XML字符串
     */
    @Override
    public String definitionXml(String processDefinitionId) {
        // 创建StringBuilder用于拼接XML字符串
        StringBuilder xml = new StringBuilder();
        // 通过流程定义ID获取流程定义对象
        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(processDefinitionId);
        // 获取流程定义的资源输入流
        InputStream inputStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
        // 读取输入流中的内容，并以UTF-8编码转换为字符串后追加到StringBuilder中
        xml.append(IoUtil.read(inputStream, StandardCharsets.UTF_8));
        // 将StringBuilder中的XML字符串转换为String并返回
        return xml.toString();
    }

    @Override
    public ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId) {
        if (StrUtil.isEmpty(deploymentId)) {
            return null;
        }
        return QueryUtils.definitionQuery().deploymentId(deploymentId).singleResult();
    }

    @Override
    public void suspendOldVersionProcessDefinition(String deploymentId) {
        ProcessDefinition oldDefinition = getProcessDefinitionByDeploymentId(deploymentId);
        if (oldDefinition == null) {
            return;
        }
        if (!oldDefinition.isSuspended()) {
            // suspendProcessInstances = false，进行中的任务，不进行挂起。
            // 原因：只要新的流程不允许发起即可，老流程继续可以执行。
            repositoryService.suspendProcessDefinitionById(oldDefinition.getId(), false, null);
        }
    }

    @Override
    public void suspendProcessDefinitionByKey(String processDefinitionKey) {
        List<ProcessDefinition> list = QueryUtils.definitionQuery().processDefinitionKey(processDefinitionKey).active().list();
        for (ProcessDefinition processDefinition : list) {
            repositoryService.suspendProcessDefinitionById(processDefinition.getId(), true, null);
        }
    }

    @Override
    public List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds) {
        if (CollUtil.isEmpty(deploymentIds)) {
            return emptyList();
        }
        return QueryUtils.definitionQuery().deploymentIds(deploymentIds).list();
    }

}
